#include "stdafx.h"

float getRandomMinMax( float fMin, float fMax )
{
    float fRandNum = (float)rand () / RAND_MAX;
    return fMin + (fMax - fMin) * fRandNum;
}

D3DXVECTOR3 getRandomVector( float fMin, float fMax )
{
	D3DXVECTOR3 vVector;

    vVector.z = getRandomMinMax( fMin, fMax );
    
    float radius = (float)sqrt(fMax - vVector.z * vVector.z);
    
    float t = getRandomMinMax( -D3DX_PI, D3DX_PI );

    vVector.x = (float)cosf(t) * radius;
    vVector.y = (float)sinf(t) * radius;

	return vVector;
}

ParticleEmitter::ParticleEmitter() :	m_VBOffset(0),
										m_Flush(512),
										m_Discard(2048),
										m_pActiveList(NULL),
										m_pFreeList(NULL),
										//m_pPlanes(NULL),
										m_ActiveCount(0),
										m_CurrentTime(0.0f),
										m_LastUpdate(NULL),
										m_TexFile(NULL),
										m_ptexParticle(NULL),
										m_MaxParticles(1),
										m_NumToRelease(1),
										m_ReleaseInterval(1.0f),
										m_LifeCycle(1.0f),
										m_Size(1.0f),
										m_AirResistence(true),
										m_VelocityVar(1.0f),
										m_pVB(nullptr),
										m_RandomSpreadMin(0),
										m_RandomSpreadMax(0),
										IDrawable(D3DXVECTOR3(), D3DXVECTOR3(), nullptr, nullptr)
{
	m_Color         = D3DXCOLOR(1.0f,1.0f,1.0f,1.0f);
	m_Position        = D3DXVECTOR3(0.0f,0.0f,0.0f);
	m_Velocity        = D3DXVECTOR3(0.0f,0.0f,0.0f);
	m_Gravity         = D3DXVECTOR3(0.0f,0.0f,0.0f);
	m_Wind            = D3DXVECTOR3(0.0f,0.0f,0.0f);
}

ParticleEmitter::~ParticleEmitter()
{
    InvalidateDeviceObjects();

    while( m_pActiveList )
    {
        Particle* pParticle = m_pActiveList;
        m_pActiveList = pParticle->m_pNext;
        delete pParticle;
    }
    m_pActiveList = NULL;

    while( m_pFreeList )
    {
        Particle *pParticle = m_pFreeList;
        m_pFreeList = pParticle->m_pNext;
        delete pParticle;
    }
    m_pFreeList = NULL;

	if( m_TexFile != NULL )
	{
		delete [] m_TexFile;
		m_TexFile = NULL;
	}

    if( m_ptexParticle != NULL )
        m_ptexParticle->Release();
}

void ParticleEmitter::SetTexture( const std::string& file )
{
	if( m_TexFile != NULL )
	{
		delete [] m_TexFile;
		m_TexFile = NULL;
	}
    
    m_TexFile = new char[file.length() + 1];

    if( m_TexFile != NULL )
		strcpy_s( m_TexFile, file.length() +1, file.c_str() );
}

LPDIRECT3DTEXTURE9 &ParticleEmitter::GetTextureObject()
{
    return m_ptexParticle;
}

HRESULT ParticleEmitter::Init()
{
    HRESULT hr;

    if( FAILED( hr = RestoreDeviceObjects() ) )
        return hr;

    D3DCAPS9 d3dCaps;
    globals::d3dDevice->GetDeviceCaps( &d3dCaps );
    m_MaxPointSize = d3dCaps.MaxPointSize;

    if( d3dCaps.FVFCaps & D3DFVFCAPS_PSIZE )
        m_DeviceSupportsPSIZE = true;
    else
        m_DeviceSupportsPSIZE = false;

    if( FAILED( D3DXCreateTextureFromFile( globals::d3dDevice, m_TexFile, &m_ptexParticle ) ) )
        return E_FAIL;

    return S_OK;
}

HRESULT ParticleEmitter::Update( FLOAT fElpasedTime )
{
    Particle  *pParticle;
    Particle **ppParticle;
    D3DXVECTOR3 vOldPosition;

    m_CurrentTime += fElpasedTime;

    ppParticle = &m_pActiveList;

    while( *ppParticle )
    {
        pParticle = *ppParticle;

        float fTimePassed  = m_CurrentTime - pParticle->m_fInitTime;

        if( fTimePassed >= m_LifeCycle )
        {
            *ppParticle = pParticle->m_pNext;
            pParticle->m_pNext = m_pFreeList;
            m_pFreeList = pParticle;

            --m_ActiveCount;
        }
        else
        {
            pParticle->m_vCurVel += m_Gravity * fElpasedTime;

            if( m_AirResistence == true )
                pParticle->m_vCurVel += (m_Wind - pParticle->m_vCurVel) * fElpasedTime;

            vOldPosition = pParticle->m_vCurPos;
            pParticle->m_vCurPos += pParticle->m_vCurVel * fElpasedTime;

            ppParticle = &pParticle->m_pNext;
        }
    }

    if( m_CurrentTime - m_LastUpdate > m_ReleaseInterval )
    {
        m_LastUpdate = m_CurrentTime;
    
        for( DWORD i = 0; i < m_NumToRelease; ++i )
        {
            if( m_pFreeList )
            {
                pParticle = m_pFreeList;
                m_pFreeList = pParticle->m_pNext;
            }
            else
            {
                if( m_ActiveCount < m_MaxParticles )
                {
                    if( NULL == ( pParticle = new Particle ) )
                        return E_OUTOFMEMORY;
                }
            }

            if( m_ActiveCount < m_MaxParticles )
            {
                pParticle->m_pNext = m_pActiveList;
                m_pActiveList = pParticle;
                
                pParticle->m_vCurVel = m_Velocity;

                if( m_VelocityVar != 0.0f )
                {
                    D3DXVECTOR3 vRandomVec = getRandomVector(m_RandomSpreadMin, m_RandomSpreadMax);
                    pParticle->m_vCurVel += vRandomVec * m_VelocityVar;
                }

                pParticle->m_fInitTime  = m_CurrentTime;
                pParticle->m_vCurPos    = m_Position;
                
                ++m_ActiveCount;
            }
        }
    }

    return S_OK;
}

void ParticleEmitter::RestartParticleSystem( void )
{
	Particle  *pParticle;
	Particle **ppParticle;

	ppParticle = &m_pActiveList;

	while( *ppParticle )
	{
		pParticle = *ppParticle;

		*ppParticle = pParticle->m_pNext;
		pParticle->m_pNext = m_pFreeList;
		m_pFreeList = pParticle;

		--m_ActiveCount;
	}
}

void ParticleEmitter::render()
{
    HRESULT hr;
	
	globals::d3dDevice->SetTexture( 0, m_ptexParticle );
    globals::d3dDevice->SetRenderState( D3DRS_POINTSIZE,     FtoDW(m_Size) );

    Particle    *pParticle = m_pActiveList;
    PointVertex *pVertices;
    DWORD        dwNumParticlesToRender = 0;

    m_VBOffset += m_Flush;

    if( m_VBOffset >= m_Discard )
        m_VBOffset = 0;

    if( FAILED( hr = m_pVB->Lock( m_VBOffset * sizeof(PointVertex), 
                                  m_Flush * sizeof(PointVertex),  
                                  (void**) &pVertices, 
                                  m_VBOffset ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD)))
    {
        return;
    }

    while( pParticle )
    {
        D3DXVECTOR3 vPos(pParticle->m_vCurPos);
        D3DXVECTOR3 vVel(pParticle->m_vCurVel);

        pVertices->posit = vPos;
        pVertices->color = m_Color;
        pVertices++;

        if( ++dwNumParticlesToRender == m_Flush )
        {
            m_pVB->Unlock();

			globals::d3dDevice->SetStreamSource( 0, m_pVB, 0, sizeof(PointVertex) );
			globals::d3dDevice->SetFVF( PointVertex::FVF_Flags );

            if( FAILED(hr = globals::d3dDevice->DrawPrimitive( D3DPT_POINTLIST, 
                m_VBOffset, dwNumParticlesToRender)))
            {
                return;
            }

            m_VBOffset += m_Flush;

            if( m_VBOffset >= m_Discard )
                m_VBOffset = 0;

            if( FAILED( hr = m_pVB->Lock( 
                m_VBOffset * sizeof(PointVertex), 
                m_Flush    * sizeof(PointVertex), 
                (void**) &pVertices, 
                m_VBOffset ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD)))
            {
                return;
            }

            dwNumParticlesToRender = 0;
        }

        pParticle = pParticle->m_pNext;
    }

    m_pVB->Unlock();

    if( dwNumParticlesToRender )
    {
		globals::d3dDevice->SetStreamSource( 0, m_pVB, 0, sizeof(PointVertex) );
		globals::d3dDevice->SetFVF( PointVertex::FVF_Flags );

        if(FAILED(hr = globals::d3dDevice->DrawPrimitive( D3DPT_POINTLIST, m_VBOffset, 
                                                  dwNumParticlesToRender )))
            return;
    }
}

HRESULT ParticleEmitter::RestoreDeviceObjects()
{
    HRESULT hr;

    if( FAILED( hr = globals::d3dDevice->CreateVertexBuffer( 
        m_Discard * sizeof(PointVertex), 
        D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY | D3DUSAGE_POINTS, 
        PointVertex::FVF_Flags, // Our custom FVF
        D3DPOOL_DEFAULT, 
        &m_pVB, NULL )))
    {
        return E_FAIL;
    }

    return S_OK;
}

HRESULT ParticleEmitter::InvalidateDeviceObjects()
{
	if( m_pVB != nullptr )
	{
        m_pVB->Release();
		m_pVB = nullptr;
	}

    return S_OK;
}


void ParticleEmitter::SetMinRandomSpread(float min)
{
	m_RandomSpreadMin = min;
}

void ParticleEmitter::SetMaxRandomSpread(float max)
{
	m_RandomSpreadMax = max;
}

float ParticleEmitter::GetMinRandomSpread()
{
	return m_RandomSpreadMin;
}

float ParticleEmitter::GetMaxRandomSpread()
{
	return m_RandomSpreadMax;
}



TCHAR* fakeGet()
{
	return nullptr;
}


void ParticleEmitter::toLua()
{
	using luabind::def;
	using luabind::class_;

	luabind::module(lua::State())
	[
		class_<ParticleEmitter>("ParticleEmitter").
		def(luabind::constructor<>()).
		def("initialize", &ParticleEmitter::Init).
		def("update", &ParticleEmitter::Update).
		def("render", &ParticleEmitter::render).
		property("size", &ParticleEmitter::GetSize, &ParticleEmitter::SetSize).
		property("maxParticles", &ParticleEmitter::GetMaxParticles, &ParticleEmitter::SetMaxParticles).
		property("numToEmit", &ParticleEmitter::GetNumToRelease, &ParticleEmitter::SetNumToRelease).
		property("emitIntervall", &ParticleEmitter::GetReleaseInterval, &ParticleEmitter::SetReleaseInterval).
		property("lifeCycle", &ParticleEmitter::GetLifeCycle, &ParticleEmitter::SetLifeCycle).
		property("maxPointSize", &ParticleEmitter::GetMaxPointSize).
		property("color", &ParticleEmitter::GetColor, &ParticleEmitter::SetColor).
		property("position", &ParticleEmitter::GetPosition, &ParticleEmitter::SetPosition).
		property("velocity", &ParticleEmitter::GetVelocity, &ParticleEmitter::SetVelocity).
		property("gravity", &ParticleEmitter::GetGravity, &ParticleEmitter::SetGravity).
		property("wind", &ParticleEmitter::GetWind, &ParticleEmitter::SetWind).
		property("airResistance", &ParticleEmitter::GetAirResistence, &ParticleEmitter::SetAirResistence).
		property("velocityAmount", &ParticleEmitter::GetVelocityVar, &ParticleEmitter::SetVelocityVar).
		property("texture", &fakeGet, &ParticleEmitter::SetTexture).
		property("minRandomSpread", &ParticleEmitter::GetMinRandomSpread, &ParticleEmitter::SetMinRandomSpread).
		property("maxRandomSpread", &ParticleEmitter::GetMaxRandomSpread, &ParticleEmitter::SetMaxRandomSpread)
	];
}